#include "xml.h"
#include <libxml/parser.h>
#include <libxml/tree.h>

static jsvalue handleXmlParse(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1)
		return JSVAL_VOID;
	
	NSString *content = [jsasstrRetain(cx, args[0]) autorelease];
    
	xmlDocPtr doc; /* the resulting document tree */
	
    doc = xmlReadDoc([content UTF8String], "data.xml", NULL, 0);
	if (!doc)
		return JSVAL_NULL;
	
	jsvalue XMLDocumentClass = jsget(cx, cx->slots[11], 0, "XMLElement");
	
	jsvalue rv = jsnew(cx, XMLDocumentClass, nil, 0);
	jssetslot(cx, rv, 0, "_dom", (int) doc | 1);
	jssetslot(cx, rv, 0, "_owner", rv);
	xmlNodePtr	root = xmlDocGetRootElement(doc);
	if (root) {
		jssetslot(cx, rv, 0, "_node", (int) root | 1);
	}
	
	return rv;
}

static jsvalue handleXmlReadFile(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1)
		return JSVAL_VOID;
	
	NSString *path = [jsasstrRetain(cx, args[0]) autorelease];
    
	xmlDocPtr doc; /* the resulting document tree */
	
    doc = xmlReadFile([path UTF8String], NULL, 0);
	if (!doc)
		return JSVAL_NULL;
	
	jsvalue XMLDocumentClass = jsget(cx, cx->slots[11], 0, "XMLElement");
	
	jsvalue rv = jsnew(cx, XMLDocumentClass, nil, 0);
	jssetslot(cx, rv, 0, "_owner", rv);
	jssetslot(cx, rv, 0, "_dom", (int) doc | 1);
	xmlNodePtr	root = xmlDocGetRootElement(doc);
	if (root) {
		jssetslot(cx, rv, 0, "_node", (int) root | 1);
	}

	return rv;
}

static jsvalue xmlDocumentConstructor(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	return JSVAL_VOID;
}

static jsvalue xmlListConstructor(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	return JSVAL_VOID;
}

static jsvalue wrapNode(jscontext *cx, jsvalue owner, xmlNodePtr node) {
	jsvalue XMLElementClass = jsget(cx, cx->slots[11], 0, "XMLElement");
	jsvalue v = jsnew(cx, XMLElementClass, nil, 0);
	jssetslot(cx, v, 0, "_node", (int) node | 1);
	jssetslot(cx, v, 0, "_owner", owner);
	return v;
}

static jsvalue xmlCustomGet(jscontext *cx, jsvalue me, int h, jsvalue name) {
	int ndoc = jsgetslot(cx, me, 0, "_node");
	if (!ndoc)
		return JSVAL_VOID;
	xmlNodePtr node = (xmlNodePtr) (ndoc & ~1);
	
	if (!node)
		return JSVAL_VOID;
	
	jsvalue arr = jsnewarray(cx);

	jsaddroot(cx, &arr);
	
	int count = 0;
	
	for (xmlNodePtr p = node->children; p; p = p->next) {
		if (equalStringsJC(JSVAL_TO_STRING(name), (const char *) p->name)) {
			jssetelem(cx, arr, INT_TO_JSVAL(count++), (int) p | 1);
		}
	}

	jsvalue xmlListClass = jsget(cx, cx->slots[11], 0, "XMLList");

	jsvalue list = jsnew(cx, xmlListClass, nil, 0);
	jssetslot(cx, list, 0, "_items", arr);
	jssetslot(cx, list, 0, "_owner", me);

	jsremoveroot(cx, &arr);

	return list;
}

static jsvalue xmlCustomGetAttr(jscontext *cx, jsvalue me, int h, jsvalue name) {
	int ndoc = jsgetslot(cx, me, 0, "_node");
	if (!ndoc)
		return JSVAL_VOID;
	xmlNodePtr node = (xmlNodePtr) (ndoc & ~1);
	
	if (!node)
		return JSVAL_VOID;
	
	
	NSString *strname = jsasstrRetain(cx, name);
	xmlChar *attr = xmlGetProp(node, [strname UTF8String]);
	[strname release];
	
	if (attr) {
		jsvalue rv = jsstrFromCstring(cx, attr);
		xmlFree(attr);
		return rv;
	}
	
	return JSVAL_NULL;
}

static jsvalue xmlCount(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1)
			return JSVAL_VOID;
	jsvalue items = jsgetslot(cx, args[0], 0, "_items");
	jsvalue nroot = jsgetslot(cx, args[0], 0, "_node");
	if (items) {
			return jsnumi(cx, jsarraylength(items));
	}
	if (nroot) {
			return INT_TO_JSVAL(1);
	}
	return INT_TO_JSVAL(0);
}

static jsvalue xmlListCustomGet(jscontext *cx, jsvalue me, int h, jsvalue name) {
	jsvalue owner = jsgetslot(cx, me, 0, "_owner");
	jsvalue items = jsgetslot(cx, me, 0, "_items");
	if (!items)
		return JSVAL_VOID;

	int itemCount = jsarraylength(items);

	if (jsisnum(name)) {
		int idx = jsasint(cx, name);
		if (idx >= 0 && idx < itemCount) {
			xmlNodePtr node = (xmlNodePtr) (jsgetelem(cx, items, jsnumi(cx, idx)) & ~1);
			return wrapNode(cx, owner, node);
		}
		return JSVAL_VOID;
	} else if (!JSVAL_IS_STRING(name))
		name = STRING_TO_JSVAL(jsasstr(cx, name));		
	
	jsvalue arr = jsnewarray(cx);
	jsaddstackroot(cx, arr);
	int count = 0;
	
	for (int idx = 0; idx < itemCount; idx++) {
		xmlNodePtr node = (xmlNodePtr) (jsgetelem(cx, items, jsnumi(cx, idx)) & ~1);
		for (xmlNodePtr p = node->children; p; p = p->next) {
			if (equalStringsJC(JSVAL_TO_STRING(name), (const char *) p->name)) {
				jssetelem(cx, arr, INT_TO_JSVAL(count++), (int) p | 1);
			}
		}
	}

	jsvalue xmlListClass = jsget(cx, cx->slots[11], 0, "XMLList");
	
	jsvalue list = jsnew(cx, xmlListClass, nil, 0);
	jssetslot(cx, list, 0, "_items", arr);
	jssetslot(cx, list, 0, "_owner", jsgetslot(cx, me, 0, "_owner"));
	return list;
}

static jsvalue xmlName(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1)
		return JSVAL_VOID;
	jsvalue items = jsgetslot(cx, args[0], 0, "_items");
	jsvalue nroot = jsgetslot(cx, args[0], 0, "_node");
	if (items) {
		if (jsarraylength(items) > 0) {
			nroot = jsgetelem(cx, items, INT_TO_JSVAL(0));
		}
	}
	if (nroot) {
		xmlNodePtr root = (xmlNodePtr) (nroot & ~1);
		return jsstrFromNSString(cx, [NSString stringWithUTF8String:root->name]);
	}
	return JSVAL_VOID;
}

static jsvalue xmlTextContent(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1)
		return JSVAL_VOID;
	jsvalue items = jsgetslot(cx, args[0], 0, "_items");
	jsvalue nroot = jsgetslot(cx, args[0], 0, "_node");
	if (items) {
		if (jsarraylength(items) > 0) {
			nroot = jsgetelem(cx, items, INT_TO_JSVAL(0));
			xmlNodePtr root = (xmlNodePtr) (nroot & ~1);
			xmlChar *text = xmlNodeGetContent(root);
			jsvalue rv = jsstrFromNSString(cx, [NSString stringWithUTF8String:(const char *) text]);
			xmlFree(text);
			return rv;
		}
	}
	else if (nroot) {
		xmlNodePtr root = (xmlNodePtr) (nroot & ~1);
		
		xmlChar *text = xmlNodeGetContent(root);
		jsvalue rv = jsstrFromNSString(cx, [NSString stringWithUTF8String:(const char *) text]);
		xmlFree(text);
		return rv;
	}
	return JSVAL_VOID;
}

static jsvalue xmlChildren(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1)
		return JSVAL_VOID;

	jsvalue obj = args[0];
	
	jsvalue result = jsnewarray(cx);
	int count = 0;
	jsaddstackroot(cx, result);
	jsvalue items = jsgetslot(cx, obj, 0, "_items");
	jsvalue owner = jsgetslot(cx, obj, 0, "_owner");
	if (items) {
		int itemCount = jsarraylength(items);
		for (int idx = 0; idx < itemCount; idx++) {
			xmlNodePtr node = (xmlNodePtr) (jsgetelem(cx, items, jsnumi(cx, idx)) & ~1);
			for (xmlNodePtr p = node->children; p; p = p->next) {
				if (p->type == XML_ELEMENT_NODE) {
					jssetelem(cx, result, INT_TO_JSVAL(count++), wrapNode(cx, owner, p));
				}
			}
		}
	} else {
		jsvalue nroot = jsgetslot(cx, obj, 0, "_node");
		if (nroot) {
			xmlNodePtr root = (xmlNodePtr) (nroot & ~1);
			for (xmlNodePtr p = root->children; p; p = p->next) {
				if (p->type == XML_ELEMENT_NODE) {
					jssetelem(cx, result, INT_TO_JSVAL(count++), wrapNode(cx, owner, p));
				}
			}
		}
	}
	
	return result;
}

void xmlDocumentDestructor(jscontext *cx, jsvalue obj) {
	jsvalue dom = jsgetslot(cx, obj, 0, "_dom");
	if (dom) {
		xmlDocPtr doc = (xmlDocPtr) (dom & ~1);
		xmlFreeDoc(doc);
	}
}

void registerXML(jscontext *cx) {
	jsvalue xmlClass, xmlListClass;
	jsvalue XML;
	
	cx->slots[11] = XML = jsnewobject(cx);
	jsset(cx, XML, 0, "readFile", bindFunction(cx, handleXmlReadFile));
	jsset(cx, XML, 0, "parse", bindFunction(cx, handleXmlParse));
	jsset(cx, XML, 0, "XMLElement", xmlClass = bindFunction(cx, xmlDocumentConstructor));
	jsset(cx, XML, 0, "XMLList", xmlListClass = bindFunction(cx, xmlListConstructor));
	jsset(cx, XML, 0, "elements", bindFunction(cx, xmlChildren));
	jsset(cx, XML, 0, "name", bindFunction(cx, xmlName));
	jsset(cx, XML, 0, "textContent", bindFunction(cx, xmlTextContent));
	jsset(cx, XML, 0, "innerText", bindFunction(cx, xmlTextContent));
	jsset(cx, XML, 0, "count", bindFunction(cx, xmlCount));
	
	((jsfunction *) xmlClass)->customGet = xmlCustomGet;
	((jsfunction *) xmlClass)->customGetAttr = xmlCustomGetAttr;
	((jsfunction *) xmlListClass)->customGet = xmlListCustomGet;
	
	jsfunction *xmlClassFn = (jsfunction *) xmlClass;
	xmlClassFn->destructor = xmlDocumentDestructor;
}
